#version 310 es

layout (local_size_x = 4, local_size_y = 4) in;

layout (binding = 0) readonly buffer InBufUV0 {
    uint data[];
} in_buf_uv0;

layout (binding = 1) readonly buffer CoordX0 {
    vec4 data[];
} coordx0;

layout (binding = 2) readonly buffer CoordY0 {
    vec4 data[];
} coordy0;

layout (binding = 3) readonly buffer InBufUV1 {
    uint data[];
} in_buf_uv1;

layout (binding = 4) readonly buffer CoordX1 {
    vec4 data[];
} coordx1;

layout (binding = 5) readonly buffer CoordY1 {
    vec4 data[];
} coordy1;

layout (binding = 6) writeonly buffer OutBufUV {
    uvec2 data[];
} out_buf_uv;

layout (binding = 7) readonly buffer MaskBuf {
    uint data[];
} mask_buf;

uniform uint in_img_width0;
uniform uint in_img_width1;
uniform uint out_img_width;
uniform uint out_offset_x;

uniform uint coords_width0;
uniform uint coords_width1;

#define UNIT_SIZE 4u

void geomap_uv (vec4 in_uv_x, vec4 in_uv_y, out vec4 out_uv0, out vec4 out_uv1);

void main ()
{
    uvec2 coord_pos = gl_GlobalInvocationID.y * uvec2 (coords_width0, coords_width1) + gl_GlobalInvocationID.x;

    vec4 in_uv_x0 = coordx0.data[coord_pos.x];
    vec4 in_uv_y0 = coordy0.data[coord_pos.x];
    vec4 in_uv_x1 = coordx1.data[coord_pos.y];
    vec4 in_uv_y1 = coordy1.data[coord_pos.y];
    vec4 in_uv_x = vec4 (in_uv_x0.xy, in_uv_x1.xy);
    vec4 in_uv_y = vec4 (in_uv_y0.xy, in_uv_y1.xy);

    vec4 out_uv0, out_uv1;
    geomap_uv (in_uv_x, in_uv_y, out_uv0, out_uv1);

    vec4 mask = unpackUnorm4x8 (mask_buf.data[gl_GlobalInvocationID.x]);
    vec4 out0 = (out_uv0 - out_uv1) * mask.xxyy + out_uv1;
    out0 = clamp (out0, 0.0f, 1.0f);

    in_uv_x = vec4 (in_uv_x0.zw, in_uv_x1.zw);
    in_uv_y = vec4 (in_uv_y0.zw, in_uv_y1.zw);
    geomap_uv (in_uv_x, in_uv_y, out_uv0, out_uv1);

    vec4 out1 = (out_uv0 - out_uv1) * mask.zzww + out_uv1;
    out1 = clamp (out1, 0.0f, 1.0f);

    uint out_pos = gl_GlobalInvocationID.y * out_img_width + out_offset_x + gl_GlobalInvocationID.x;
    out_buf_uv.data[out_pos] = uvec2 (packUnorm4x8 (out0), packUnorm4x8 (out1));
}

void geomap_uv (vec4 in_uv_x, vec4 in_uv_y, out vec4 out_uv0, out vec4 out_uv1)
{
    uvec4 x00 = uvec4 (in_uv_x);
    uvec4 y00 = uvec4 (in_uv_y);
    uvec4 x01 = x00 + 1u;
    uvec4 y01 = y00;
    uvec4 x10 = x00;
    uvec4 y10 = y00 + 1u;
    uvec4 x11 = x01;
    uvec4 y11 = y10;

    vec4 fract_x = fract (in_uv_x);
    vec4 fract_y = fract (in_uv_y);
    vec4 weight00 = (1.0f - fract_x) * (1.0f - fract_y);
    vec4 weight01 = fract_x * (1.0f - fract_y);
    vec4 weight10 = (1.0f - fract_x) * fract_y;
    vec4 weight11 = fract_x * fract_y;

    uvec4 x00_floor = x00 >> 2u;    // divided by UNIT_SIZE
    uvec4 x01_floor = x01 >> 2u;
    uvec4 x10_floor = x10 >> 2u;
    uvec4 x11_floor = x11 >> 2u;
    uvec4 x00_fract = (x00 % UNIT_SIZE) >> 1u;
    uvec4 x01_fract = (x01 % UNIT_SIZE) >> 1u;
    uvec4 x10_fract = (x10 % UNIT_SIZE) >> 1u;
    uvec4 x11_fract = (x11 % UNIT_SIZE) >> 1u;

    uvec4 img_width = uvec4 (in_img_width0, in_img_width0, in_img_width1, in_img_width1);
    uvec4 index00 = y00 * img_width + x00_floor;
    uvec4 index01 = y01 * img_width + x01_floor;
    uvec4 index10 = y10 * img_width + x10_floor;
    uvec4 index11 = y11 * img_width + x11_floor;

    vec4 out_uv00, out_uv01, out_uv10, out_uv11;
    vec4 value = unpackUnorm4x8 (in_buf_uv0.data[index00.x]);
    out_uv00.xy = (x00_fract.x == 0u) ? value.xy : value.zw;
    value = (index01.x == index00.x) ? value : unpackUnorm4x8 (in_buf_uv0.data[index01.x]);
    out_uv01.xy = (x01_fract.x == 0u) ? value.xy : value.zw;
    value = unpackUnorm4x8 (in_buf_uv0.data[index10.x]);
    out_uv10.xy = (x10_fract.x == 0u) ? value.xy : value.zw;
    value = (index11.x == index10.x) ? value : unpackUnorm4x8 (in_buf_uv0.data[index11.x]);
    out_uv11.xy = (x11_fract.x == 0u) ? value.xy : value.zw;

    value = unpackUnorm4x8 (in_buf_uv0.data[index00.y]);
    out_uv00.zw = (x00_fract.y == 0u) ? value.xy : value.zw;
    value = (index01.y == index00.y) ? value : unpackUnorm4x8 (in_buf_uv0.data[index01.y]);
    out_uv01.zw = (x01_fract.y == 0u) ? value.xy : value.zw;
    value = unpackUnorm4x8 (in_buf_uv0.data[index10.y]);
    out_uv10.zw = (x10_fract.y == 0u) ? value.xy : value.zw;
    value = (index11.y == index10.y) ? value : unpackUnorm4x8 (in_buf_uv0.data[index11.y]);
    out_uv11.zw = (x11_fract.y == 0u) ? value.xy : value.zw;

    out_uv0 = out_uv00 * weight00.xxyy + out_uv01 * weight01.xxyy +
              out_uv10 * weight10.xxyy + out_uv11 * weight11.xxyy;

    value = unpackUnorm4x8 (in_buf_uv1.data[index00.z]);
    out_uv00.xy = (x00_fract.z == 0u) ? value.xy : value.zw;
    value = (index01.z == index00.z) ? value : unpackUnorm4x8 (in_buf_uv1.data[index01.z]);
    out_uv01.xy = (x01_fract.z == 0u) ? value.xy : value.zw;
    value = unpackUnorm4x8 (in_buf_uv1.data[index10.z]);
    out_uv10.xy = (x10_fract.z == 0u) ? value.xy : value.zw;
    value = (index11.z == index10.z) ? value : unpackUnorm4x8 (in_buf_uv1.data[index11.z]);
    out_uv11.xy = (x11_fract.z == 0u) ? value.xy : value.zw;

    value = unpackUnorm4x8 (in_buf_uv1.data[index00.w]);
    out_uv00.zw = (x00_fract.w == 0u) ? value.xy : value.zw;
    value = (index01.w == index00.w) ? value : unpackUnorm4x8 (in_buf_uv1.data[index01.w]);
    out_uv01.zw = (x01_fract.w == 0u) ? value.xy : value.zw;
    value = unpackUnorm4x8 (in_buf_uv1.data[index10.w]);
    out_uv10.zw = (x10_fract.w == 0u) ? value.xy : value.zw;
    value = (index11.w == index10.w) ? value : unpackUnorm4x8 (in_buf_uv1.data[index11.w]);
    out_uv11.zw = (x11_fract.w == 0u) ? value.xy : value.zw;

    out_uv1 = out_uv00 * weight00.zzww + out_uv01 * weight01.zzww +
              out_uv10 * weight10.zzww + out_uv11 * weight11.zzww;
}

